home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Classes / XText / XTAction_parser.m < prev    next >
Encoding:
Text File  |  1992-04-14  |  11.5 KB  |  437 lines

  1. /*    This file is part of the XText package (version 0.8)
  2.     Mike Dixon, April 1992
  3.     
  4.     Copyright (c) 1992 Xerox Corporation.  All rights reserved.
  5.  
  6.     Use and copying of this software and preparation of derivative works based
  7.     upon this software are permitted.  This software is made available AS IS,
  8.     and Xerox Corporation makes no warranty about the software or its
  9.     performance.
  10. */
  11.  
  12. #import "ErrorStream.h"
  13. #import "XTAction.h"
  14. #import <sys/types.h>
  15. #import <nextdev/keycodes.h>
  16. #import <stdio.h>
  17. #import <stdlib.h>
  18. #import <string.h>
  19.  
  20. /*  This file contains all the routines to parse the argument to the
  21.     addBindings:estream: method; it's the most complicated part of the
  22.     whole package.  The strategy is simple recursive descent, and we
  23.     make no attempt to recover from errors.
  24.  
  25.     The grammar supported is somewhat more general than necessary; for
  26.     example you can nest sequences of instructions, which currently serves
  27.     no purpose (unless you wanted to get around the maximum sequence
  28.     length...).  The idea is just to make it easy to add more complex
  29.     control structure later, if that turns out to be useful.
  30. */
  31.  
  32. #define MAX_SEQUENCE_LENGTH 16        //    max number of actions in a sequence
  33. #define MAX_SELECTOR_LENGTH 32        //    max length of a selector name
  34. #define MAX_STRING_LENGTH 256        //    max length of a string argument
  35. #define MAX_ARGS 2                    //    max number of args to a message
  36.     // (Note that if you increase MAX_ARGS you'll also have to add a new
  37.     //    subclass of XTAction and augment parse_msg to use it.)
  38.  
  39. #define MAX_KEYS 8                    //    max number of keys affected by a
  40.                                     //    single binding
  41.  
  42. #define PRE_ERROR_CONTEXT 32        //    number of characters displayed before
  43. #define POST_ERROR_CONTEXT 16        //    and after a syntax error
  44.  
  45. typedef keyCode keySet[MAX_KEYS];    //    set of keys an action will be bound to
  46.  
  47.  
  48. #define ALPHA(c) \
  49.     (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || (c == '_'))
  50.  
  51. #define WHITE(c) \
  52.     ((c == ' ') || (c == '\n') || (c == '\t') || (c == '\r'))
  53.  
  54. #define DIGIT(c) \
  55.     ((c >= '0') && (c <= '9'))
  56.  
  57.  
  58. /*    skip_whitespace advances over any white space and returns the first
  59.     non-whitespace character.
  60.  
  61.     Like the rest of the parsing routines, it's passed a pointer to a
  62.     char pointer, which is advanced as the string is consumed.
  63. */
  64.  
  65. char skip_whitespace(const char **p)
  66. {
  67.     while (WHITE(**p))
  68.         ++*p;
  69.     return **p;
  70. }
  71.  
  72. /*    report_syntax_error is used to report all syntax errors detected during
  73.     parsing.  The args are
  74.         error        optional information about the type of error
  75.         p            a pointer to the place at which the error was detected
  76.         start        a pointer to the beginning of the string being parsed
  77.         errs        the ErrorStream
  78.  
  79.     The message will contain some or all of the string surrounding the
  80.     error to help identify the problem.
  81. */
  82.  
  83. void report_syntax_error(const char *error, const char *p, const char *start,
  84.                          ErrorStream *errs)
  85. {
  86.     char msg[100+PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT];
  87.     const char *prefix;
  88.     int  prefix_length;
  89.  
  90.     // display at most PRE_ERROR_CONTEXT characters before the error point...
  91.  
  92.     if (start < (p - PRE_ERROR_CONTEXT)) {
  93.         prefix = p - PRE_ERROR_CONTEXT;
  94.         prefix_length = PRE_ERROR_CONTEXT;
  95.     } else {
  96.         prefix = start;
  97.         prefix_length = p-start;
  98.     }
  99.  
  100.     // ... and at most POST_ERROR_CONTEXT characters after, except that if
  101.     // there weren't many characters before we can put even more after.
  102.  
  103.     sprintf(msg, "Syntax error%s in binding:\n    %.*s (here) %.*s",
  104.                 error, prefix_length, prefix,
  105.                 (PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT-prefix_length), p);
  106.     [errs report:msg];
  107. }
  108.  
  109. XTAction *parse_action(const char **p, NXZone *z,
  110.                        const char *start, ErrorStream *errs);
  111.  
  112. /*    parse_seq parses a '{}'-delimited, ';'-separated sequence of actions
  113.     and constructs an XTSeqAction out of them.  The args are
  114.         p        pointer to the current position pointer
  115.         z        zone in which to allocate the XTActions
  116.         start    the beginning of the string (for reporting errors)
  117.         errs    the ErrorStream
  118.  
  119.     If there are no errors, the new XTAction is returned; otherwise the
  120.     result is nil.
  121. */
  122.  
  123. XTAction *parse_seq(const char **p, NXZone *z,
  124.                     const char *start, ErrorStream *errs)
  125. {
  126.     // we accumulate the actions in an array on the stack, and then copy
  127.     // them into the specified zone when we find out how many there were.
  128.  
  129.     XTAction *actions[MAX_SEQUENCE_LENGTH];
  130.     int num_actions = 0;
  131.     XTAction **copied_actions = 0;
  132.     char c;
  133.  
  134.     // skip over the open brace
  135.     ++*p;
  136.     while (1) {
  137.         c = skip_whitespace(p);
  138.         if (c == '}') {
  139.             ++*p;
  140.             if (num_actions == 1)
  141.                 return actions[0];
  142.             else if (num_actions > 0) {
  143.                 size_t size = num_actions * sizeof(XTAction *);
  144.                 copied_actions = NXZoneMalloc(z, size);
  145.                 memcpy(copied_actions, actions, size);
  146.             }
  147.             return [[XTSeqAction allocFromZone:z]
  148.                         initLength:num_actions actions:copied_actions];
  149.             }
  150.         else if (c == ';')
  151.             ++*p;
  152.         else {
  153.             if (num_actions >= MAX_SEQUENCE_LENGTH) {
  154.                 report_syntax_error(" (sequence too long)", *p, start, errs);
  155.                 return nil;
  156.             }
  157.             if (!(actions[num_actions++] = parse_action(p, z, start, errs)))
  158.                 return nil;
  159.         }
  160.     }
  161. }
  162.  
  163. /*    parse_arg parses a message argument, which must be either an integer
  164.     or a '"'-delimited string.  The args are the same as parse_seq, with
  165.     one addition:
  166.         result        a pointer to where the result should be stored
  167.  
  168.     Only a few escape sequences are recognized: \n, \t, \\, and \".  It
  169.     would be easy to add more.
  170.  
  171.     If there are no errors, the result (coerced to an int) will be stored
  172.     in *result and parse_arg will return true; otherwise it returns false.
  173. */
  174.  
  175. BOOL parse_arg(int *result, const char **p, NXZone *z,
  176.                const char *start, ErrorStream *errs)
  177. {
  178.     char arg[MAX_STRING_LENGTH];
  179.     int arg_length = 0;
  180.     char c;
  181.     char *copied_arg;
  182.  
  183.     c = skip_whitespace(p);
  184.     if (DIGIT(c) || (c == '-') || (c == '+'))
  185.         *result = strtol(*p, p, 0);        // ought to check for overflow...
  186.     else if (c == '"') {
  187.         while (1) {
  188.             c = *++*p;
  189.             switch (c) {
  190.             case 0:
  191.                 report_syntax_error(" (unterminated string)", *p, start, errs);
  192.                 return NO;
  193.             case '"':
  194.                 ++*p;
  195.                 goto at_end;
  196.             case '\\':
  197.                 c = *++*p;
  198.                 switch (c) {
  199.                 case 'n':    c = '\n'; break;
  200.                 case 't':    c = '\t'; break;
  201.                 case '\\':
  202.                 case '"':              break;
  203.                 default:
  204.                     report_syntax_error(" (unknown escape sequence)",
  205.                                         *p, start, errs);
  206.                     return NO;
  207.                 }
  208.             }
  209.             if (arg_length >= MAX_STRING_LENGTH) {
  210.                 report_syntax_error(" (string too long)", *p, start, errs);
  211.                 return NO;
  212.             }
  213.             arg[arg_length++] = c;
  214.         }
  215.     at_end:
  216.         copied_arg = NXZoneMalloc(z, arg_length+1);
  217.         memcpy(copied_arg, arg, arg_length);
  218.         copied_arg[arg_length] = '\0';
  219.         *result = (int)copied_arg;
  220.     } else {
  221.         report_syntax_error("", *p, start, errs);
  222.         return NO;
  223.     }
  224.     return YES;
  225. }
  226.  
  227. /*    parse_msg parses a single message action, such as
  228.             replaceSel:"foobar" length:3
  229.     The args and result are the same as for parse_seq.
  230. */
  231.  
  232. XTAction *parse_msg(const char **p, NXZone *z,
  233.                     const char *start, ErrorStream *errs)
  234. {
  235.     char sel_name[MAX_SELECTOR_LENGTH];
  236.     int args[MAX_ARGS];
  237.     int sel_length = 0;
  238.     int num_args = 0;
  239.     char c;
  240.     SEL sel;
  241.     char *error;
  242.  
  243.     c = **p;
  244.     while (1) {
  245.         sel_name[sel_length++] = c;
  246.         if (sel_length >= MAX_SELECTOR_LENGTH) {
  247.             error = " (selector too long)";
  248.             goto syntax_error;
  249.         }
  250.         ++*p;
  251.         if (c == ':') {
  252.             if (num_args >= MAX_ARGS) {
  253.                 error = " (too many args)";
  254.                 goto syntax_error;
  255.             }
  256.             if (!parse_arg(&args[num_args++], p, z, start, errs))
  257.                 return nil;
  258.             skip_whitespace(p);
  259.         }
  260.         c = **p;
  261.         if (!(ALPHA(c) || DIGIT(c) || c == ':'))
  262.             break;
  263.     }
  264.     sel_name[sel_length] = '\0';
  265.     sel = sel_getUid(sel_name);
  266.     if (sel == 0) {
  267.         error = " (unknown selector)";
  268.         goto syntax_error;
  269.     }
  270.     return num_args == 0
  271.                 ? [[XTMsg0Action allocFromZone:z] initSel:sel]
  272.         : num_args == 1
  273.                    ? [[XTMsg1Action allocFromZone:z] initSel:sel arg:args[0]]
  274.         : [[XTMsg2Action allocFromZone:z] initSel:sel arg:args[0] arg:args[1]];
  275.  
  276. syntax_error:
  277.     report_syntax_error(error, *p, start, errs);
  278.     return nil;
  279. }
  280.  
  281. /*    parse_action parses an action, which currently must be either a message
  282.     to be sent to the XText object or a sequence of actions.  The args are
  283.     the same as parse_seq.
  284. */
  285.  
  286. XTAction *parse_action(const char **p, NXZone *z,
  287.                        const char *start, ErrorStream *errs)
  288. {
  289.     char c;
  290.  
  291.     c = skip_whitespace(p);
  292.     if (ALPHA(c))
  293.         return parse_msg(p, z, start, errs);
  294.     if (c == '{')
  295.         return parse_seq(p, z, start, errs);
  296.     report_syntax_error(((c == 0) ? " (unexpected end)" : ""),
  297.                         *p, start, errs);
  298.     return nil;
  299. }
  300.  
  301. /*    parse_keys parses a specification of the keys an action is to be bound
  302.     to.  A specification is a ','-separated sequence, terminated by a '=',
  303.     where each element is zero or more modifiers ('c', 's', 'a', or 'm')
  304.     followed by either a hex key code or ' followed by the character generated
  305.     by the key.  In the latter case there may be several keys that generate
  306.     the character; each is added to the set.  If there are no errors, the
  307.     key codes are stored in keys and true is returned; otherwise false is
  308.     returned.  If there are fewer than MAX_KEYS keys, the first unused entry
  309.     in keys is set to 0 (which happens to be an invalid key code).
  310. */
  311.  
  312. BOOL parse_keys(keySet keys, const char **p,
  313.                 const char *start, ErrorStream *errs)
  314. {
  315.     int num_keys = 0;
  316.     int key = 0;
  317.     int i;
  318.     char c;
  319.     BOOL found_one;
  320.     char *error;
  321.  
  322.     while (1) {
  323.         c = skip_whitespace(p);
  324.         found_one = NO;
  325.         switch (c) {
  326.         case 'c': key |= 1; break;
  327.         case 's': key |= 2; break;
  328.         case 'a': key |= 4; break;
  329.         case 'm': key |= 8; break;
  330.         case '0': case '1': case '2': case '3': case '4': case '5':
  331.             key += (c - '0') << 8;
  332.             c = *++*p;
  333.             if (DIGIT(c))
  334.                 key += (c - '0') << 4;
  335.             else if ((c >= 'a') && (c <= 'f'))
  336.                 key += (10 + c - 'a') << 4;
  337.             else if ((c >= 'A') && (c <= 'F'))
  338.                 key += (10 + c - 'A') << 4;
  339.             else {
  340.                 error = "";
  341.                 goto syntax_error;
  342.             }
  343.             if (num_keys >= MAX_KEYS) {
  344.                 error = " (too many keys)";
  345.                 goto syntax_error;
  346.             }
  347.             keys[num_keys++] = key;
  348.             found_one = YES;
  349.             break;
  350.         case '\'':
  351.             c = *++*p;
  352.             found_one = NO;
  353.             // skip over the first couple of keys, which don't exist and/or
  354.             // don't generate ascii codes
  355.             for (i = 6; i < (2*MAPPED_KEYS); ++i) {
  356.                 // if a key generates the same character shifted and unshifted,
  357.                 // don't add them both.
  358.                 if ((ascii[i] == c) && (((i&1) == 0) || (ascii[i-1] != c))) {
  359.                     if (num_keys >= MAX_KEYS) {
  360.                         error = " (too many keys)";
  361.                         goto syntax_error;
  362.                     }
  363.                     keys[num_keys++] = ((i&0xfe) << 3) | key | ((i&1) << 1);
  364.                     found_one = YES;
  365.                 }
  366.             }
  367.             if (!found_one) {
  368.                 error = " (no key for this char)";
  369.                 goto syntax_error;
  370.             }
  371.             break;
  372.         default:
  373.             error = "";
  374.             goto syntax_error;
  375.         }
  376.         ++*p;
  377.         if (found_one) {
  378.             c = skip_whitespace(p);
  379.             ++*p;
  380.             if (c == ',')
  381.                 {}                    // go back for more
  382.             else if (c == '=') {
  383.                 if (num_keys < MAX_KEYS)
  384.                     keys[num_keys] = 0;
  385.                 return YES;
  386.             } else {
  387.                 error = "";
  388.                 goto syntax_error;
  389.             }
  390.         }
  391.     }
  392.     
  393. syntax_error:
  394.     report_syntax_error(error, *p, start, errs);
  395.     return NO;
  396. }                    
  397.  
  398. @implementation XTDispatchAction(parsing)
  399.  
  400. /*    Finally, here's the method we've been preparing to implement.
  401.     Note that any XTActions generated will be allocated in the same
  402.     zone as the dispatch action.
  403. */
  404.  
  405. - addBindings:(const char *)bindings estream:errs
  406. {
  407.     keySet keys;
  408.     char c;
  409.     const char *cp = bindings;
  410.     XTAction *a;
  411.     NXZone *z = [self zone];
  412.     int i;
  413.  
  414.     if (!errs) errs = [ErrorStream default];
  415.     while (1) {
  416.         c = skip_whitespace(&cp);
  417.         if (c == 0)
  418.             return self;
  419.         if (c == ';')
  420.             ++cp;
  421.         else {
  422.             if (!parse_keys(keys, &cp, bindings, errs))
  423.                 return self;
  424.             if (!(a = parse_action(&cp, z, bindings, errs)))
  425.                 return self;
  426.             for (i = 0; i < MAX_KEYS; ++i) {
  427.                 if (keys[i])
  428.                     [self bindKey:keys[i] toAction:a estream:errs];
  429.                 else
  430.                     break;
  431.             }
  432.         }
  433.     }
  434. }
  435.  
  436. @end
  437.